#include "mainwidget.h"
#include <QtGui>
#include <QtOpenGL>

#include <math.h>
#include <iostream>
#include <fstream>
#include "../glutil/QGLBindTexture.h"
#include "../dataengine/VibModelFileManager.h"
#include "../dataengine/VibTerrainFileManager.h"
#include "../glutil/GLSupport.h"

MainWidget::MainWidget(QWidget *parent) : QGLWidget(QGLFormat(QGL::DoubleBuffer | QGL::AlphaChannel),parent)
{
	m_camera = new glutil::FreeCamera(geom::Point3D(0,0.3,0), geom::Vector3D(0,0,-1),45);
	m_pickingBuffer = NULL;
	m_pickingBufferUpToDate = false;
	m_selectedTree = -1;
	for(int i=0;i<4;++i)
		m_mustMoveDirections[i] = false;
}

MainWidget::~MainWidget()
{
	delete m_camera;
	delete m_dummyTreeRenderer;
	delete m_dummyImpostor;
}

void MainWidget::initializeGL()
{
    makeCurrent();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);

    GLfloat ambientLight[]={0.4,0.35,0.35,1.0};
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambientLight);

    glShadeModel(GL_SMOOTH);
	
	dataengine::VibModelFileManager vibloader("data/dummy.vib");
	m_dummyObject = vibloader.loadFile();
	m_dummyTreeRenderer = new glutil::ModelRenderer((*m_dummyObject).getModel(0));
	m_dummyImpostor = new glutil::Impostor((*m_dummyObject).getModel(0),256);

	m_postProcess = new PostProcess(100,100);
	this->m_currentTerrain = geom::TerrainModel();
	const int NUMPOINTS = 100;
	const float RADIUS = 28.5;
	this->m_currentTerrain.addPoint(geom::Point3D(0,0,0));
	for(int i=0;i<NUMPOINTS;++i)
		this->m_currentTerrain.addPoint(geom::Point3D(RADIUS * sin( ((float)i)/NUMPOINTS * 2* M_PI), 0, RADIUS * cos(((float)i)/NUMPOINTS*2*M_PI)));
	for(int i=1;i<NUMPOINTS;++i)
		this->m_currentTerrain.addTriangle(geom::IntTri(0,i-1,i));
	this->m_currentTerrain.addTriangle(geom::IntTri(0,NUMPOINTS-1,1));

	this->m_currentTerrain.setBaseColor(0.3398437,0.445312,0);

	this->m_currentTerrain.generateNormals();
    this->m_terrainRenderer.reset(new glutil::TerrainRenderer(&m_currentTerrain));

    m_grass.reset(new sceneelems::Grass());
    QPixmap q3 = QPixmap(QString("data/grassShort.png"));
    QPixmap q4 = QPixmap(QString("data/flor1.png"));
    QPixmap q5 = QPixmap(QString("data/arbushto1.png"));
	m_grass->addGrassType(glutil::QGLBindTexture(q3), q3.width(), q3.height(), 2.01,0.4,m_currentTerrain);
	m_grass->addGrassType(glutil::QGLBindTexture(q4), q4.width(), q4.height(), 0.04,0.4,m_currentTerrain);
	m_grass->addGrassType(glutil::QGLBindTexture(q5), q5.width(), q5.height(), 0.02,0.5,m_currentTerrain);

	m_sky.initialize();
}

void MainWidget::setGLLight()
{
    float ambientLight0[4] = {0.2,0.2,0.2,1.0f};
    float diffuseLight0[4] = {0.4,0.4,0.4,1.0f};
    float specularLight0[4] = {0.0,0.0,0.0,1.0f};
    float lightPosition0 [4] = {20, 30, 40,0.0};

    glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight0);
    glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight0);
    glLightfv(GL_LIGHT0,GL_SPECULAR,specularLight0);
    glLightfv(GL_LIGHT0,GL_POSITION,lightPosition0);

    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
}

void MainWidget::renderPickingBuffer()
{
	std::map<RendererInfo,glutil::ModelRenderer>::iterator it;
	std::map<RendererInfo,glutil::ModelRenderer>::iterator end = m_treeRenderers.end();
	geom::Point3D camPos = m_camera->position();
	glUseProgram(0);
	glClearColor(1.0,1.0,1.0,1.0);
	glViewport(0,0,widgetWidth,widgetHeight);
	m_camera->setAsGLCamera();
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);
	float IMPOSTOR_DISTANCE = std::numeric_limits<float>::max();

	for(it = m_treeRenderers.begin(); it!=end; ++it)
		it->second.renderAll(false,m_camera, IMPOSTOR_DISTANCE,-1);

	m_dummyTreeRenderer->renderAll(false,m_camera, IMPOSTOR_DISTANCE,-1);



	delete [] m_pickingBuffer;

	m_pickingBuffer = new float[widgetWidth * widgetHeight * 4];

	glReadBuffer(GL_BACK);
	glReadPixels( 0,0,
				 widgetWidth, widgetHeight,GL_RGBA,GL_FLOAT,
				 m_pickingBuffer);

	m_pickingBufferUpToDate = true;
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
}

void MainWidget::pickTree(int x, int y)
{
	if(!m_pickingBufferUpToDate)
		renderPickingBuffer();

	float fr = m_pickingBuffer[4*(x+widgetWidth*y)] * 128.0f;
	float fg = m_pickingBuffer[4*(x+widgetWidth*y)+1] * 128.0f;
	float fb = m_pickingBuffer[4*(x+widgetWidth*y)+2] * 128.0f;

	int r = lroundf(fr);
	int g = lroundf(fg);
	int b = lroundf(fb);

	// We take the tree ID from the color of the picking buffer
	int id = r*128*128 + g*128 + b;

	m_selectedTree = id;

	emit(selectedTree(m_selectedTree));
}


void MainWidget::paintGL()
{
    makeCurrent();
	static bool initializedSky = false;
	if(!initializedSky)
	{
		emit(initializeSky());
		initializedSky=true;
	}
    glViewport(0, 0, widgetWidth, widgetHeight);

	if(glutil::GLSupport::areFBOsSupported() && glutil::GLSupport::areShadersSupported())
		m_postProcess->beginCapture();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	set2DCamera();
	glLoadIdentity();
    glDepthMask(GL_FALSE);
    const float degreesToRadians = M_PI/180.0;
    m_sky.draw(m_camera->viewVector(), m_camera->FOV() * degreesToRadians, m_camera->FOV()*widgetWidth/widgetHeight* degreesToRadians);
    glDepthMask(GL_TRUE);
    m_camera->setAsGLCamera();
    setGLLight();
	glEnable(GL_COLOR_MATERIAL);
    glDisable(GL_TEXTURE_2D);
    if(m_terrainRenderer.get()!=NULL)
        m_terrainRenderer->render();

    glDisable(GL_LIGHT0);
	std::map<RendererInfo,glutil::ModelRenderer>::iterator it;
	std::map<RendererInfo,glutil::ModelRenderer>::iterator end = m_treeRenderers.end();
	geom::Point3D camPos = m_camera->position();

	m_grass->draw(widgetWidth);

	float IMPOSTOR_DISTANCE = 20;
	if(!glutil::GLSupport::areFBOsSupported()) // If FBOs are not supported, we disable impostor rendering
		IMPOSTOR_DISTANCE = std::numeric_limits<float>::max();

	if(glutil::GLSupport::areFBOsSupported())
	{
		std::map<RendererInfo,glutil::Impostor>::iterator it1;
		std::map<RendererInfo,glutil::Impostor>::iterator end1 = m_treeImpostors.end();
		for(it1 = m_treeImpostors.begin(); it1!=end1; ++it1)
			it1->second.renderAllFartherThan(camPos, IMPOSTOR_DISTANCE);
	}

	for(it = m_treeRenderers.begin(); it!=end; ++it)
		it->second.renderAll(true,m_camera, IMPOSTOR_DISTANCE, m_selectedTree);

	if(glutil::GLSupport::areFBOsSupported())
		m_dummyImpostor->renderAllFartherThan(camPos,IMPOSTOR_DISTANCE);

	m_dummyTreeRenderer->renderAll(true,m_camera,IMPOSTOR_DISTANCE,m_selectedTree);


	if(glutil::GLSupport::areFBOsSupported() && glutil::GLSupport::areShadersSupported())
	{
		m_postProcess->endCapture();
		m_postProcess->doPostProcess();
	}
}

void MainWidget::set2DCamera()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1,1,-1,1,0.1,100.0);
    glMatrixMode(GL_MODELVIEW);
}

void MainWidget::resizeGL(int width, int height)
{
    widgetWidth = width;
    widgetHeight = height;
    m_postProcess->widgetResized(width,height);
	m_camera->setAspectRatio(float(width)/ float(height));
	m_pickingBufferUpToDate = false;
    updateGL();
}

void MainWidget::wheelEvent(QWheelEvent * event)
{
	m_camera->advance(-event->delta()/1000.0);
	m_pickingBufferUpToDate = false;
    updateGL();

}

void MainWidget::mousePressEvent(QMouseEvent *event)
{
    lastPos = event->pos();
	if(event->button() == Qt::RightButton)
	{
		pickTree(event->x(),widgetHeight - event->y());
		updateGL();
	}
}

void MainWidget::keyPressEvent ( QKeyEvent * event )
{
	if(event->key() == Qt::Key_W)
		m_mustMoveDirections[M_FRONT] = true;

	if(event->key() == Qt::Key_S)
		m_mustMoveDirections[M_BACK] = true;

	if(event->key() == Qt::Key_A)
		m_mustMoveDirections[M_LEFT] = true;

	if(event->key() == Qt::Key_D)
		m_mustMoveDirections[M_RIGHT] = true;
	timer.start(1,this);
}

void MainWidget::keyReleaseEvent ( QKeyEvent * event )
{
	if(event->key() == Qt::Key_W)
		m_mustMoveDirections[M_FRONT] = false;

	if(event->key() == Qt::Key_S)
		m_mustMoveDirections[M_BACK] = false;

	if(event->key() == Qt::Key_A)
		m_mustMoveDirections[M_LEFT] = false;

	if(event->key() == Qt::Key_D)
		m_mustMoveDirections[M_RIGHT] = false;

	timer.start(1,this);
}
void MainWidget::timerEvent(QTimerEvent *)
{
	float speed = 0.1;
	if(QApplication::keyboardModifiers() && Qt::ShiftModifier)
		speed *=5;
	timer.stop();
	if(m_mustMoveDirections[M_FRONT])
		m_camera->advance(speed);
	if(m_mustMoveDirections[M_BACK])
		m_camera->advance(-speed);
	if(m_mustMoveDirections[M_LEFT])
		m_camera->strafeLeft(speed);
	if(m_mustMoveDirections[M_RIGHT])
		m_camera->strafeRight(speed);

	for(int i=0;i<4;++i)
		if(m_mustMoveDirections[i])
		{
			m_pickingBufferUpToDate = false;
			timer.start(60,this);
			break;
		}
	updateGL();
}

void MainWidget::mouseMoveEvent(QMouseEvent *event)
{
    int dx = lastPos.x() - event->x();
    int dy = lastPos.y() - event->y();

	if(event->buttons() & Qt::RightButton)
		return;

	m_camera->mouseLookTurn(dx/200.0,dy/200.0);

    lastPos = event->pos();
	m_pickingBufferUpToDate = false;
    updateGL();
}

void MainWidget::reconstructForestFromData()
{
	std::map<RendererInfo,glutil::ModelRenderer>::iterator it;
	std::map<RendererInfo,glutil::ModelRenderer>::iterator end = m_treeRenderers.end();

	for(it = m_treeRenderers.begin(); it!=end; ++it)
		it->second.clearInstances();

	std::map<RendererInfo,glutil::Impostor>::iterator iti;
	std::map<RendererInfo,glutil::Impostor>::iterator endi = m_treeImpostors.end();
	for(iti = m_treeImpostors.begin(); iti!=endi;++iti)
		iti->second.clearInstances();

	m_dummyTreeRenderer->clearInstances();
	m_dummyImpostor->clearInstances();
	int numTrees = currentForest.numberOfTrees();

	for(int i=0;i<numTrees;++i)
	{
		const dataengine::VorestTreeInfo & treeInfo = currentForest.at(i);

		RendererInfo rinfo;
		int species = treeInfo.speciesID();
		rinfo.speciesFile = currentSpecies.getFileForSpecies(species);
		rinfo.fileModel = treeInfo.modelID();
		std::map<RendererInfo, glutil::ModelRenderer>::iterator it = m_treeRenderers.find(rinfo);
        glutil::GLInstanceInfo glinfo = treeInfo.asGLInstanceInfo();

        float terrainHeight = this->m_currentTerrain.getHeightAtCoords(glinfo.m_location[0], glinfo.m_location[2]);
		glinfo.m_location.at(1) = terrainHeight;

		if(it == m_treeRenderers.end())
            m_dummyTreeRenderer->addInstance(glinfo); // Muestra un dummy tree cuando no se haya definido un modelo
		else
            it->second.addInstance(glinfo);

		std::map<RendererInfo, glutil::Impostor>::iterator it2 = m_treeImpostors.find(rinfo);
		if(it2 == m_treeImpostors.end())
            m_dummyImpostor->addInstance(glinfo);
		else
            it2->second.addInstance(glinfo);
	}
	m_pickingBufferUpToDate = false;
}

void MainWidget::forestChanged(slotsdata::ForestState state)
{
	currentForest = state;
	reconstructForestFromData();

	m_pickingBufferUpToDate = false;
	updateGL();
}

